Исследование было запрошено менеджерами мобильной игры "Космические братья". Разработчики хотят внедрить монетизацию и при этом не быть назойливыми и раздражающими для своих пользователей. В настоящее время в планах внедрение рекламного банера на экран постройки, но заказчик исследования хочет рассмотреть другие модели монетизации.
В нашем распоряжении данные о когортах пользователей, которые начали пользоваться приложением в период с 4 по 10 мая включительно (пользователи привлеченные разными рекламными каналами). Известны расходы на привлечение пользователей, прибыль с показа одной рекламы (0,07 у.е.), типы событий и построенных объектов с датой-временем совершения действия.
Для работы были предоставленны три датасета: ad_costs.csv (рсходы на рекламу), game_actions.csv (внутри игровые действия) и user_source.csv (канал прилечения пользователя).
Результатом исследования станут полный текст исследования в JupiterNotebook, презентация с основными выводами в формате pdf и интерактивный дешборд в Tableau
Выбор новой модели монетизации, вызывающей меньшее раздражение у игроков, при этом все еще окупающейся
В нашем распоряжении три датасета: ad_costs.csv (стоимость рекламы), game_actions.csv (действия в игре), user_source.csv (источник привлечения пользователя). Загрузим имеющиеся данные и посмотрим что содержится в таблицах, информацию о хранимых типах данных, если ли пропуски и явные дубликаты и проведем предобработку при необходимости
<class 'pandas.core.frame.DataFrame'> RangeIndex: 28 entries, 0 to 27 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 source 28 non-null object 1 day 28 non-null object 2 cost 28 non-null float64 dtypes: float64(1), object(2) memory usage: 800.0+ bytes
None
Колонок с пропущенным значением: 0 Количество явных дубликатов: 0
| source | day | cost | |
|---|---|---|---|
| 0 | facebook_ads | 2020-05-03 | 935.882786 |
| 1 | facebook_ads | 2020-05-04 | 548.354480 |
| 2 | facebook_ads | 2020-05-05 | 260.185754 |
| 3 | facebook_ads | 2020-05-06 | 177.982200 |
| 4 | facebook_ads | 2020-05-07 | 111.766796 |
Колонка day отображается типом object, хотя содержит дату. Поменяем тип данных на корректный.
В колонке cost округлим значения до сотых
Проверим что все изменения применились
<class 'pandas.core.frame.DataFrame'> RangeIndex: 28 entries, 0 to 27 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 source 28 non-null object 1 day 28 non-null datetime64[ns] 2 cost 28 non-null float64 dtypes: datetime64[ns](1), float64(1), object(1) memory usage: 800.0+ bytes
None
| source | day | cost | |
|---|---|---|---|
| 0 | facebook_ads | 2020-05-03 | 935.88 |
| 1 | facebook_ads | 2020-05-04 | 548.35 |
| 2 | facebook_ads | 2020-05-05 | 260.19 |
| 3 | facebook_ads | 2020-05-06 | 177.98 |
| 4 | facebook_ads | 2020-05-07 | 111.77 |
Данные обработаны и готовы к исследованию. Перейдем к следующему датасету
<class 'pandas.core.frame.DataFrame'> RangeIndex: 135640 entries, 0 to 135639 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 event_datetime 135640 non-null object 1 event 135640 non-null object 2 building_type 127957 non-null object 3 user_id 135640 non-null object 4 project_type 1866 non-null object dtypes: object(5) memory usage: 5.2+ MB
None
Колонок с пропущенным значением: 2 Количество явных дубликатов: 1
| event_datetime | event | building_type | user_id | project_type | |
|---|---|---|---|---|---|
| 0 | 2020-05-04 00:00:01 | building | assembly_shop | 55e92310-cb8e-4754-b622-597e124b03de | NaN |
| 1 | 2020-05-04 00:00:03 | building | assembly_shop | c07b1c10-f477-44dc-81dc-ec82254b1347 | NaN |
| 2 | 2020-05-04 00:00:16 | building | assembly_shop | 6edd42cc-e753-4ff6-a947-2107cd560710 | NaN |
| 3 | 2020-05-04 00:00:16 | building | assembly_shop | 92c69003-d60a-444a-827f-8cc51bf6bf4c | NaN |
| 4 | 2020-05-04 00:00:35 | building | assembly_shop | cdc6bb92-0ccb-4490-9866-ef142f09139d | NaN |
Наблюдения по датасету:
Так как датасет содержит информацию о событиях и пользователях посмотрим более детально на его содержание
Всего событий в логе: 135640 Всего пользователей в логе: 13576 В среднем на пользователя приходится 10 событий Дата самого раннего события: 2020-05-04 00:00:01 Дата самого позднего события: 2020-06-05 12:32:49
Приведем event_datetime в типу datetime
Проверим, где чаще всего встречаются пропуски. Посмотрим на примере колонки event при каких событиях информация building_type отсутвует
array(['building', 'finished_stage_1', 'project'], dtype=object)
Уникальные сооружение в событии building: ['assembly_shop' 'spaceport' 'research_center'] Уникальные сооружение в событии finished_stage_1: [nan] Уникальные сооружение в событии project: [nan]
Типы событий finished_stage_1 и project не содержат инфорамции о типах строения, в то время как событие building не содержит пропусков. Переименуем очевидный пропуск в прочее (Other).
Пропуски остались только в колонке project_type. Посмотрим их уникальные значения
array([nan, 'satellite_orbital_assembly'], dtype=object)
Уникальные типы проекта в событии building: [nan] Уникальные типы проекта в событии finished_stage_1: [nan] Уникальные типы проекта в событии project: ['satellite_orbital_assembly']
В данном датасете в типе проекта находятся два значения: satellite_orbital_assembly (разработка орбитальной сборки спутников) и nan. Возможно в дальнейшем типов проектов будет больше, но в нашем случае, так как значения по своей сути в настоящее время отвечает на вопрос "Это шаг с разработкой орбитальной сборки спутников?" то логично будет сменить название колонки на SAO (satellite_orbital_assembly), а значение внутри сменить на булевые True и False
event_dt datetime64[ns] event object building_type object user_id object SAO bool dtype: object
Пропуски заполнены, типы данных сменены, удалим очевидный дубликат и посмотрим как выглядит датасет после обработки
<class 'pandas.core.frame.DataFrame'> Int64Index: 135639 entries, 0 to 135639 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 event_dt 135639 non-null datetime64[ns] 1 event 135639 non-null object 2 building_type 127956 non-null object 3 user_id 135639 non-null object 4 SAO 135639 non-null bool dtypes: bool(1), datetime64[ns](1), object(3) memory usage: 5.3+ MB
None
Колонок с пропущенным значением: 1 Количество явных дубликатов: 0
| event_dt | event | building_type | user_id | SAO | |
|---|---|---|---|---|---|
| 0 | 2020-05-04 00:00:01 | building | assembly_shop | 55e92310-cb8e-4754-b622-597e124b03de | False |
| 1 | 2020-05-04 00:00:03 | building | assembly_shop | c07b1c10-f477-44dc-81dc-ec82254b1347 | False |
| 2 | 2020-05-04 00:00:16 | building | assembly_shop | 6edd42cc-e753-4ff6-a947-2107cd560710 | False |
| 3 | 2020-05-04 00:00:16 | building | assembly_shop | 92c69003-d60a-444a-827f-8cc51bf6bf4c | False |
| 4 | 2020-05-04 00:00:35 | building | assembly_shop | cdc6bb92-0ccb-4490-9866-ef142f09139d | False |
Данные проверены и обработаны. Переходим к последнему датасету
<class 'pandas.core.frame.DataFrame'> RangeIndex: 13576 entries, 0 to 13575 Data columns (total 2 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 user_id 13576 non-null object 1 source 13576 non-null object dtypes: object(2) memory usage: 212.2+ KB
None
Колонок с пропущенным значением: 0 Количество явных дубликатов: 0
| user_id | source | |
|---|---|---|
| 0 | 0001f83c-c6ac-4621-b7f0-8a28b283ac30 | facebook_ads |
| 1 | 00151b4f-ba38-44a8-a650-d7cf130a0105 | yandex_direct |
| 2 | 001aaea6-3d14-43f1-8ca8-7f48820f17aa | youtube_channel_reklama |
| 3 | 001d39dc-366c-4021-9604-6a3b9ff01e25 | instagram_new_adverts |
| 4 | 002f508f-67b6-479f-814b-b05f00d4e995 | facebook_ads |
Данные о 13576 пользователях и каналах, благодаря которым они скачали приложение. Пропусков и явных дубликатов нет. Данные соответствуют заявленному типу. Таблица готова к работе.
В ходе предобработки были внесены следующие изменения:
ad_costs.csv
game_actions.csv
Для анализа создадим новую переменную, в которую будем вносить уточнения для построения графиков и расчетов. Проверим как распределялось количество всех событий в разные дни.
Посмотрим максимальное количество пользователей, посетивших приложение:
Картина по количеству пользователей за каждый день исследования похожа на количество совершаемых событий, а именно:
Посмотрим долю событий и типа построек в разрезе всех событий
Посмотрим на распределение событий по дням
В разрезе событий график похож на общий график количества событий. Для начала посмотрим график распределения построек по дням, а позже вернемся к детализации событий
Text(0.5, 1.0, 'Распределение построения зданий по дням, за период с 04.05.2020 по 05.06.2020')
Вот и причина резкого роста активности пользователей, и резкого её же сокращения. С первого дня исследования по 10 мая встречается анамально высокий рост построек assemly_shop. С 11 мая строительство такого типа полностью прекратилось. Из остальных наблюдений:
посмотрим детальней разбивку по постройкам зданий типа assembly_shop в разбивке когорт по дням
Показатели колябляться от 7698 по 7869, с учетом того что одновремено в игре находилось от 5860 по 9219 пользователей. В каждой кагорте наблюдатся тенденция, в которой после первого игрового дня общее количество строений типа assembly_shop сокращается в более чем два-три раза и сохраняется на этом уровне.
Вернемся к общеми разрезу по событиям. Уберем из разреза самое часто встречающееся событие building и посмотрим распределение по датом среди событий finished_stage_1 и project
Количество пользователей, завершивших первый уровень: 5817 Количество пользователей, завершивших проект: 1866
По условиям заданий завершение первого уровня требует от игрока выполнения одного из двух условий:
Получается пользователи, прошедшие событие project получили шанс пройти на втрой уровень в услувиях системы pve (Player versus environment / игрок против окружения). А разница игроков, не прошедших событие project, но получившие шанс пройти на втрой уровень в услувиях системы pvp (Player versus player / игрок против игрока). Поскольку исполнение проекта выделено в отдельную колонку, ее можно использовать, чтобы выделить пользователям по отдельным тактикам и сделать два отдельных датасета: для пользователей с тактиков pvp и для пользователей с такстикой pve.
Проверим какие сооружения чаще всего строили пользователи, имеющие шанс перейти на следующий уровень
По условиям заданий завершение первого уровня требует от игрока выполнения одного из двух условий:
Получается пользователи, прошедшие событие project получили шанс пройти на втрой уровень в услувиях системы pve (Player versus environment / игрок против окружения). А разница игроков, не прошедших событие project, но получившие шанс пройти на втрой уровень в услувиях системы pvp (Player versus player / игрок против игрока). Поскольку исполнение проекта выделено в отдельную колонку, ее можно использовать, чтобы выделить пользователям по отдельным тактикам и сделать два отдельных датасета: для пользователей с тактикой pvp и для пользователей с такстикой pve среди тех, которые завершили первый уровень
/tmp/ipykernel_193/3684430935.py:3: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
| event_dt | event | building_type | user_id | SAO | tactic | |
|---|---|---|---|---|---|---|
| 1 | 2020-05-04 | building | assembly_shop | c07b1c10-f477-44dc-81dc-ec82254b1347 | False | pve |
| 8 | 2020-05-04 | building | assembly_shop | 65a92713-5969-48b1-8945-bfefa5063887 | False | pve |
| 9 | 2020-05-04 | building | assembly_shop | fa352362-d258-472c-b295-2796ccc39fa3 | False | pve |
| 13 | 2020-05-04 | building | assembly_shop | 7b3b6554-7949-4065-9808-d6c79bdc2f58 | False | pve |
| 20 | 2020-05-04 | building | assembly_shop | b374304d-2f74-4cc1-a4e2-500da44a06c7 | False | pve |
/tmp/ipykernel_193/3328933810.py:2: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
| event_dt | event | building_type | user_id | SAO | tactic | |
|---|---|---|---|---|---|---|
| 2 | 2020-05-04 | building | assembly_shop | 6edd42cc-e753-4ff6-a947-2107cd560710 | False | pvp |
| 4 | 2020-05-04 | building | assembly_shop | cdc6bb92-0ccb-4490-9866-ef142f09139d | False | pvp |
| 6 | 2020-05-04 | building | assembly_shop | 4fe0f101-694b-4e31-9740-d1d87f7208ea | False | pvp |
| 11 | 2020-05-04 | building | assembly_shop | 9c200ece-3b74-44c4-8a3f-260f2486c5d7 | False | pvp |
| 15 | 2020-05-04 | building | assembly_shop | 02bf92c2-17a0-4156-be88-2054fb6b7421 | False | pvp |
Эти отдельные датафреймы пригодятся нам при проверке гипотез. Общие тенденции пользовательского поведения понятны, с частными разберемся в другой части исследования.
Расходы по привлечению пользователей по дням расположены в датасете ad_cosct.csv. Посмотрим каналы трафика, дату старта и завершения и самые дорогие и дешевые дни
Всего уникальных источников трафика: 4 Перечень источников трафика: ['facebook_ads' 'instagram_new_adverts' 'yandex_direct' 'youtube_channel_reklama'] Общая стоимость рекламной кампании 7603 Медианная стоимость рекламного дня 160 Максимальная стоимость рекламного дня 969.14 Минимальная стоимость рекламного дня 23.31 Средняя стоимость одного пользователя 0.56 Дата самого раннего события: 2020-05-03 00:00:00 Дата самого позднего события: 2020-05-09 00:00:00
Посмотрим распределение рекламного бюджета по источникам и дням
Так же в нашем распоряжении есть датафрейм user_source.csv, ответственный за источники привлечения пользователей. Посмотрим долю каналов на количество привлеченных пользователей
Чтобы посмотреть распределение пользователей по дня, сократим датасет actions до первого событие совершенного уникальным пользователем и объеденим таблицу с каналами привлечения
| user_id | event_dt | source | |
|---|---|---|---|
| 0 | 0001f83c-c6ac-4621-b7f0-8a28b283ac30 | 2020-05-06 01:07:37 | facebook_ads |
| 1 | 00151b4f-ba38-44a8-a650-d7cf130a0105 | 2020-05-06 03:09:12 | yandex_direct |
| 2 | 001aaea6-3d14-43f1-8ca8-7f48820f17aa | 2020-05-05 18:08:52 | youtube_channel_reklama |
| 3 | 001d39dc-366c-4021-9604-6a3b9ff01e25 | 2020-05-05 21:02:05 | instagram_new_adverts |
| 4 | 002f508f-67b6-479f-814b-b05f00d4e995 | 2020-05-05 13:49:58 | facebook_ads |
Посмотрим распределение затрат по стоимости рекламы на количество привлеченных ползователей по дням и увидим, какие источники рекламы выгоднее всего в соотвеношении цена/кол-во пользователей
| event_dt | source | user_id | cost | cac | |
|---|---|---|---|---|---|
| 0 | 2020-05-04 | facebook_ads | 1184 | 935.88 | 0.790439 |
| 1 | 2020-05-05 | facebook_ads | 694 | 548.35 | 0.790130 |
| 2 | 2020-05-06 | facebook_ads | 345 | 260.19 | 0.754174 |
| 3 | 2020-05-07 | facebook_ads | 224 | 177.98 | 0.794554 |
| 4 | 2020-05-08 | facebook_ads | 139 | 111.77 | 0.804101 |
Известно, что рекламу показывают при типе события building и прибыль с показа одной рекламы составляет 0,07 у.е. Ранее мы посчитали, что общие затраты на рекламную кампанию составили 7 603 у.е. Создадим таблицу с распределением прибыли на день и посмотрим динамику по дням
Суммарная выручка за исследуемый период составила 8956.92 Суммарная выручка за первую неделю 6018.88 Суммарная выручка за первые две недели 8505.49 Прибыль за исследуемый период с учетом расходов составила 1353.92
Выводы по графику кумулятивной прибыли:
Разделим пользователей, приносящих доход на когорты по времени их прихода и посмотрим как распределяется LTV, держа в уме понимание о том, что с каждым днем приток пользователей был все меньше и меньше.
Ранее мы разделили весь дата фрейм на два, в соотвествии со стратегией. Посмотрим сколько событий и пользователей вошло в каждый из них
Пользователи, предпочитающие pve стиль игры: 1866 Всего событий на pve пользователей 27333 Пользователи, предпочитающие pvp стиль игры: 3951 Всего событий на pvp пользователей 41136
Похоже, большинство игроков не завершили постройку орбитальной станции и в распределение попали как игроки со стилем игры pvp.
Посмотрим как конкретно распределяются события и постройки для каждого стиля игры:
Сравнивая pve и pvp стили игры можно выделить следующее:
Посмотрим на динамику строительства различных объекетов в ретроспектве за исследуемый период
Так как событие other в случае с pvp означает одновременно построение орбитальной станции и переход на ледующий уровень, а в pvp только переход на следующий уровень можно сделать следующие выводы:
Посмотрим на удержание плоьзователей в каждом из стилей игры
Общая картина удержания pvp и pve очень похожа, за исключением одного момента:
В начале исследования были выдвинуты 2 гипотезы, которые связаны со стилем игры:
Для проверки первой гипотезы создадим дата-сет, в котором посчитаем сколько каждый пользователь провел дней в игре с момента первой постройки и до последнего события
| event_dt | event | building_type | user_id | SAO | start | finish | days_in_game | |
|---|---|---|---|---|---|---|---|---|
| 0 | 2020-05-04 | building | assembly_shop | c07b1c10-f477-44dc-81dc-ec82254b1347 | False | 2020-05-04 | 2020-05-15 | 11.0 |
| 1 | 2020-05-06 | building | assembly_shop | c07b1c10-f477-44dc-81dc-ec82254b1347 | False | 2020-05-04 | 2020-05-15 | 11.0 |
| 2 | 2020-05-07 | building | spaceport | c07b1c10-f477-44dc-81dc-ec82254b1347 | False | 2020-05-04 | 2020-05-15 | 11.0 |
| 3 | 2020-05-07 | building | assembly_shop | c07b1c10-f477-44dc-81dc-ec82254b1347 | False | 2020-05-04 | 2020-05-15 | 11.0 |
| 4 | 2020-05-07 | building | assembly_shop | c07b1c10-f477-44dc-81dc-ec82254b1347 | False | 2020-05-04 | 2020-05-15 | 11.0 |
Сформулируем нулевую и альтернативную гипотезы следующим образом:
Нулевая гипотеза: Время прохождения уровня между пользователями, которые заканчивают уровень через реализацию проекта, и пользователями, которые заканчивают уровень победой над другим игроком одинаковое
Альтернативная гипотеза: Время прохождения уровня между пользователями, которые заканчивают уровень через реализацию проекта, и пользователями, которые заканчивают уровень победой над другим игроком различается
p-значение: 3.814709032214186e-37 Отвергаем нулевую гипотезу
Для проверки второй гипотезы возьмем два созданных ранее дата-сета pvp и pve и ограничем их постройкой типа research_center
Сформулируем нулевую и альтернативную гипотезы следующим образом:
Нулевая гипотеза: PVE пользователи строят столько же построек типа research_center, сколько и PVP пользователи
Альтернативная гипотеза: PVE пользователи и PVP пользователи строят разное количество построек типа research_center
p-значение: 5.549821034650398e-42 Отвергаем нулевую гипотезу
Обе нулевые гипотезы были отвергнуты:
В пользовательском поведении pvp и pve не так много различий. Ключевые моменты, которые могут указывать на разный подход и стиль игры определены ниже:
Так как глобальная задача исследования заключается в выборе новой модели монетизации, вызывающие меньшее раздражение у пользователей, но все еще окупающейся. Выведем прибыль, посчитанную ранее
Суммарная выручка за исследуемый период составила 8956.92 Суммарная выручка за первую неделю 5467.56 Суммарная выручка за первые две недели 8348.97 Прибыль за исследуемый период с учетом расходов составила 1353.92
Большая часть выручики уходит на рекламный бюджет. С оставшейся прибылью сложно предлагать глобальные варианты новой модели, но исходя из того, что я увидела в исследовании хочется предложить следующее:
Оставить показ рекламы при строительстве assembly_shop и spaceport, и убрать из строительства research_center. Исследовательские центры чаще всего строят pve пользователи, и хоть эта группа немногочислена, но она более лояльна. Количество построек этого типа занимает 11,05% (14 137 строения) от общего числа событий. Убрав раздражитель в виде рекламы на этом типе строения можно удержать пользователей, которые не определились со своей стратегией, и привлечь их в сторону pve варианта, что потенциально отразиться на общем количестве совершения событий типа building
Прибыль составит 364
Помимо этого стоит пересмотреть используемые рекламные каналы и перераспределить расходы на с неоправданно дорогих каналов (например facebook_ads) на те, что приносят больше пользователей и стоят дешевле (yandex_direct или youtube_channel_reklama)
Для целей исследования были предоставленны три датасета: ad_costs.csv (рсходы на рекламу), game_actions.csv (внутри игровые действия) и user_source.csv (канал прилечения пользователя).
В ходе предобработки были внесены следующие изменения:
ad_costs.csv
game_actions.csv
Стоит учитывать, что период, выбранный для исследования, частично попадает на Майские праздники и может искажать реальную картину
В результате изучения пользовательского поведения были замечены следующие тенденции:
В целях исследования пользователи были поделены на две отдельные стратегии: те, кто завершил первый уровень построив орбитальную станцию (PVE) и те, кто завершил его победив противника (PVP)
Из распределения рекламного бюджета сделаны следующие выводы:
Было проведено сравнение пользователей разного стиля игры. Сильных различий в данных выявлено не было, но были отмечены следующие различия:
Основываясь на общей картине была предложена и обоснована новая модель монетизации:
Оставить показ рекламы при строительстве assembly_shop и spaceport, и убрать из строительства research_center. (прибыль составит 364 у.е., но есть вероятность что игроки, не определившиеся с тактикой, смогут принять PVE стиль и задержаться в игре подольше)
Перед работой были выдвинуты две гипотезы:
Благодарю за внимание!